/**
 * @file ftp.c
 * @author japy (goiot@goiot.net)
 * @brief 
 * @version 0.1
 * @date 2020-07-09
 * 
 * @copyright Copyright (c) 2020
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// #include <arpa/inet.h>
#include <netdb.h>		// getaddrinfo()
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include "ftp.h"
#define DEBUG
#ifdef DEBUG
#define log_d(format,args...) {printf(format,##args);printf("\r\n");}
#else
#define log_d() 
#endif

#if 0
//创建一个socket并返回
int socket_connect(const char *host,int port)
{
    struct addrinfo *address = NULL;
    struct addrinfo hint;
    int s, opvalue;
    socklen_t slen;
    char port_str[6] = {0}; 
    
    opvalue = 8;
    slen = sizeof(opvalue);
    
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
        return -1;
    
    //设置接收和发送超时
    struct timeval timeo = {15, 0};
    setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));

      /* get host addr ok. */
    {
        /* resolve the host name. */
        memset(&hint, 0, sizeof(hint));
        sprintf(port_str,"%d",port);
        if(0 != getaddrinfo(host, port_str, &hint, &address))
            goto _exit;
    }
    
    if (connect(s, address->ai_addr, address->ai_addrlen) == -1)
    {
        close(s);
        s = -1;
    }

_exit:    
    if (address)
    {
        freeaddrinfo(address);
        address = NULL;
    }
    return s;
}
#endif
#if 1
//创建一个socket并返回
int socket_connect(const char *host,int port)
{
    struct sockaddr_in address;
    int s, opvalue;
    socklen_t slen;
    
    opvalue = 8;
    slen = sizeof(opvalue);
    memset(&address, 0, sizeof(address));
    
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
        return -1;
    
    //设置接收和发送超时
    struct timeval timeo = {15, 0};
    setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
    
    address.sin_family = AF_INET;
    address.sin_port = htons((unsigned short)port);
    
    struct hostent* server = gethostbyname(host);
    if (!server) {
        close(s);
        return -1;
    }
    
    memcpy(&address.sin_addr.s_addr, server->h_addr, server->h_length);
    
    if (connect(s, (struct sockaddr*) &address, sizeof(address)) == -1)
    {
        close(s);
        return -1;
    }
    
    return s;
}
#endif

//连接到一个ftp的服务器，返回socket
int connect_server(const char *host, int port )
{    
    int       ctrl_sock;
    char      *buf;
    int       result;
    ssize_t   len;
    
    ctrl_sock = socket_connect(host, port);
    if (ctrl_sock == -1) {
        return -1;
    }
    buf = malloc(512);
    if(!buf) return -1;
    len = recv( ctrl_sock, buf, 512, 0 );
    buf[len] = 0;
    sscanf( buf, "%d", &result );
    if ( result != 220 ) {
        close( ctrl_sock );
        free(buf);
        return -1;
    }
    free(buf);
    return ctrl_sock;
}
 
//发送命令,返回结果
int ftp_sendcmd_re( int sock, char *cmd, void *re_buf, ssize_t *len)
{
    char        *buf;
    ssize_t     r_len;
    if ( send( sock, cmd, strlen(cmd), 0 ) == -1 )
        return -1;
	buf = malloc(512);
    if(!buf) return -1;
    r_len = recv( sock, buf, 512, 0 );
    if ( r_len < 1 ) 
    {
        free(buf);
        return -1;
    }
    buf[r_len] = 0;
   
    if (len != NULL) *len = r_len;
    if (re_buf != NULL) snprintf(re_buf, 128,"%s", buf);
    log_d("%s%s",cmd, buf);
    free(buf);
    return 0;
}
 
//发送命令,返回编号
int ftp_sendcmd( int sock, char *cmd )
{
    char     buf[128];
    int      result;
    ssize_t  len;
    
    result = ftp_sendcmd_re(sock, cmd, buf, &len);
    if (result == 0)
    {
        sscanf( buf, "%d", &result );
    }
    
    return result;
}
 
//登录ftp服务器
int login_server( int sock, const char *user, const char *pwd )
{
    char    buf[128];
    int     result;
    if(!user) return 0;
    sprintf( buf, "USER %s\r\n", user );
    result = ftp_sendcmd( sock, buf );
    if ( result == 230 ) return 0;
    else if ( result == 331 ) {
        sprintf( buf, "PASS %s\r\n", pwd );
        if ( ftp_sendcmd( sock, buf ) != 230 ) return -1;
        return 0;
    }
    else
        return -1;
}
 
//连接到PASV接口
int ftp_pasv_connect( int c_sock )
{
    int     r_sock;
    int     send_re;
    ssize_t len;
    int     addr[6];
    char    buf[32];
    char    *re_buf;
    
    //设置PASV被动模式
    re_buf = malloc(512);
    if(!re_buf) return -1;
    bzero(buf, 32);
    sprintf( buf, "PASV\r\n");
    send_re = ftp_sendcmd_re( c_sock, buf, re_buf, &len);
    if (send_re == 0) {
        sscanf(re_buf, "%*[^(](%d,%d,%d,%d,%d,%d)",&addr[0],&addr[1],&addr[2],&addr[3],&addr[4],&addr[5]);
    }
    
    //连接PASV端口
    bzero(buf, 32);
    sprintf( buf, "%d.%d.%d.%d",addr[0],addr[1],addr[2],addr[3]);
    r_sock = socket_connect(buf,addr[4]*256+addr[5]);
    
    free(re_buf);
    return r_sock;
}
 
//表示类型
int ftp_type( int c_sock, char mode )
{
    char    buf[16];
    sprintf( buf, "TYPE %c\r\n", mode );
    if ( ftp_sendcmd( c_sock, buf ) != 200 )
        return -1;
    else
        return 0;
}
 
//改变工作目录
int ftp_cwd( int c_sock, char *path )
{
    char    buf[32];
    int     re;
    sprintf( buf, "CWD %s\r\n", path );
    re = ftp_sendcmd( c_sock, buf );
    if ( re != 250 )
        return -1;
    else
        return 0;
}

//下载文件
int ftp_retr( int c_sock, const char *s, unsigned int *stor_size, int *stop, rxcallback cb, void *arg)
{
    int     d_sock;
    ssize_t     len,write_len;
    char    *buf;
    int     result;
    if(!cb) return -1;
    //设置传输模式
    ftp_type(c_sock, 'I');
    
    //连接到PASV接口
    d_sock = ftp_pasv_connect(c_sock);
    if (d_sock == -1)
    {
        return -1;
    }
    buf = malloc(512);
    if(!buf) {
        close( d_sock );
        return -1;
    }
    //发送STOR命令
    bzero(buf, 512);
    sprintf( buf, "RETR %s\r\n", s );
    result = ftp_sendcmd( c_sock, buf );
    if (result >= 300 || result == 0)
    {
        free(buf);
        close( d_sock );
        return result;
    }
    
    //开始向PASV读取数据
    bzero(buf, 512);
    while ( (len = recv( d_sock, buf, 512, 0 )) > 0 ) {
        write_len = cb(arg, buf, len);
        if (write_len != len || (stop != NULL && *stop))
        {
            close( d_sock );
            free(buf);
            return -1;
        }
        
        if (stor_size != NULL)
        {
            *stor_size += write_len;
        }
    }
    close( d_sock );
    
    //向服务器接收返回值
    bzero(buf, sizeof(buf));
    len = recv( c_sock, buf, 512, 0 );
    buf[len] = 0;
    sscanf( buf, "%d", &result );
    if ( result >= 300 ) {
        free(buf);
        return result;
    }
    free(buf);
    return 0;
}

static int write_file(void *arg, const void *buf, size_t size)
{
    return fwrite(buf, 1, size, (FILE *)arg);
}

static int read_file(void *arg, void *buf, size_t size)
{
    return fread(buf, 1, size, (FILE *)arg);
}


//下载文件
int ftp_retrfile( int c_sock, const char *s, const char *d ,unsigned int *retr_size, int *stop)
{
    int     result;

    //打开本地文件
    FILE *fp = fopen(d, "wb");
    if ( fp==NULL ) return -1;
    result = ftp_retr(c_sock, s, retr_size, stop, write_file, fp);
    fclose(fp);
    return result;
}

//上传文件
int ftp_stor( int c_sock, const char *d ,unsigned int *stor_size, int *stop, txcallback cb, void *arg)
{
    int     d_sock;
    ssize_t     len,send_len;
    // char    buf[512];
    char    *buf;
    int send_re;
    int result;
    if(!cb) return -1;
    //设置传输模式
    ftp_type(c_sock, 'I');
    
    //连接到PASV接口
    d_sock = ftp_pasv_connect(c_sock);
    if (d_sock == -1)
        return -1;

    buf = malloc(512);
    if(!buf) {
        close( d_sock );
        return -1;
    }

    //发送STOR命令
    bzero(buf, sizeof(buf));
    sprintf( buf, "STOR %s\r\n", d );
    send_re = ftp_sendcmd( c_sock, buf );
    if (send_re >= 300 || send_re == 0)
    {
        free(buf);
        return send_re;
    }
    
    //开始向PASV通道写数据
    bzero(buf, sizeof(buf));
    while ( (len = cb(arg, buf, 512)) > 0)
    {
        send_len = send(d_sock, buf, len, 0);
        if (send_len != len ||
            (stop != NULL && *stop))
        {
            free(buf);
            close( d_sock );
            return -1;
        }
        
        if (stor_size != NULL)
        {
            *stor_size += send_len;
        }
    }
    close( d_sock );
    
    //向服务器接收返回值
    bzero(buf, sizeof(buf));
    len = recv( c_sock, buf, 512, 0 );
    buf[len] = 0;
    sscanf( buf, "%d", &result );
    free(buf);
    if ( result >= 300 ) {
        return result;
    }
    return 0;
}

//上传文件
int ftp_storfile( int c_sock, const char *s, const char *d ,unsigned int *stor_size, int *stop)
{
    int     result;

    //打开本地文件
    FILE *fp = fopen(s, "rb");
    if ( fp==NULL ) return -1;
    result = ftp_stor(c_sock, d, stor_size, stop, read_file, fp);
    fclose(fp);
    return result;
}
 
//链接服务器
int ftp_connect(const char *host, int port, const char *user, const char *pwd )
{
    int     c_sock;
    c_sock = connect_server( host, port );
    if ( c_sock == -1 ) return -1;
    if ( login_server( c_sock, user, pwd ) == -1 ) {
        close( c_sock );
        return -1;
    }
    return c_sock;
}

//断开服务器
int ftp_quit( int c_sock)
{
    int re = 0;
    re = ftp_sendcmd( c_sock, "QUIT\r\n" );
    close( c_sock );
    return re;
}

/**
 * @brief 从url获取ftpurlinfo信息
 * 
 * url格式：ftp://user:password@hostname:port/path/filename
 
 * @param url 
 * @param res 
 * @return int 
 */
int get_ftpurlinfo(const char *url, struct ftpurlinfo *res)
{
    char *p;
    int len = strlen(url) + 2;
    res->user=res->pw=res->host=res->path=NULL;
    res->buffer = malloc(len);
    if(!res->buffer) return -1;
    strcpy(res->buffer, url);
    res->buffer[len-1] = 0;
    res->port = 21;

    if(strncmp(res->buffer,"ftp://",6)!=0) {
        return -1;
    }
    /*获取host*/
    res->host = strstr(res->buffer,"@");
    if(res->host == NULL)
    {
        /*没有指定user和password*/
        res->host = res->buffer+6;
    }
    else 
    {
        /*获取user*/
        res->user = res->buffer+6;
        res->host[0] = 0;
        res->host ++;
        p = strstr(res->user,":");
        if(!p) return -2;
        p[0] = 0;
        p++;
        /*获取pw*/
        res->pw=p; 
    }
    /*获取path*/
    p = strstr(res->host,"/");
    if(!p) return -3;
    *p = 0;
    p = &res->buffer[len-3];
    for(;*p!=0;p--) {
        *(p+1) = *p;
    }
    p++;
    *p = '/';
    res->path = p;
    /*获取端口号*/
    p = strstr(res->host, ":");
    /* 如果指定了端口号，从url获取ftp端口*/
    if(p) {
        p[0] = 0;
        p ++;
        res->port = atoi(p);
    }
    return 0;    
}

/**
 * @brief 释放ftpurlinfo资源
 * 
 * @param res 
 */
void free_ftpurlinfo(struct ftpurlinfo *res)
{
    if(res->buffer) {
        free(res->buffer);
        res->buffer = NULL;
    }
}

/**
 * @brief 从ftp服务器下载文件
 * 
 * @param url 服务器地址，格式: ftp://test:test@192.168.0.1:21/test/v01.ota
 * @param ofile 保存文件
 * @return int 0-成功
 */
int ftp_get(const char *url, const char *ofile)
{
	int sock;
	unsigned int stor_size=0;
	int stop = 0;
	int ret;
    struct ftpurlinfo info;

    if(get_ftpurlinfo(url, &info)!=0)
    {
        printf("invalid url!\n");
        return -1;
    }

	sock = ftp_connect(info.host,info.port,info.user,info.pw);
	if(sock>0) {
		ret = ftp_retrfile(sock, info.path, ofile, &stor_size, &stop);

		if(ret==0) {
			printf("ftp receive %s success! size %d bytes\n", info.path, stor_size);
		}
		else {
			printf("receive %s error. %d\n",info.path,ret);
		}
        ftp_quit(sock);
	}
    else {
        printf("ftp user:%s password:%s connect failed %d\n",info.user,info.pw,sock);
        ret = -1;
    }
fexit:
    free_ftpurlinfo(&info);
    return ret;
}

/**
 * @brief 上传文件到ftp服务器
 * 
 * @param url 服务器地址，格式: ftp://test:test@192.168.0.1:21/test/v01.ota
 * @param sfile 本地文件
 * @return int 0-成功
 */
int ftp_put(const char *url, const char *sfile)
{
	int sock;
	unsigned int stor_size=0;
	int stop = 0;
	int ret;
    struct ftpurlinfo info;

    if(get_ftpurlinfo(url, &info)!=0)
    {
        printf("invalid url!\n");
        return -1;
    }

	sock = ftp_connect(info.host,info.port,info.user,info.pw);
	if(sock>0) {
		ret = ftp_storfile(sock, sfile, info.path, &stor_size, &stop);

		if(ret==0) {
			printf("ftp send to %s success! size %d bytes\n", info.path, stor_size);
		}
		else {
			printf("send to %s error. %d\n",info.path,ret);
		}
        ftp_quit(sock);
	}
    else {
        printf("ftp user:%s password:%s connect failed %d\n",info.user,info.pw,sock);
        ret = -1;
    }
fexit:    
    free_ftpurlinfo(&info);
    return ret;
}
